package com.bxait.toymail.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.bxait.com.to.mq.SecKillOrderTo;
import com.bxait.com.utils.R;
import com.bxait.com.vo.member.MemberRespVo;
import com.bxait.toymail.seckill.feign.CouponFeignService;
import com.bxait.toymail.seckill.feign.ProductFeignService;
import com.bxait.toymail.seckill.interceptor.LoginUserInterceptor;
import com.bxait.toymail.seckill.service.SecKillService;
import com.bxait.toymail.seckill.to.SecKillSkuRedisTo;
import com.bxait.toymail.seckill.vo.SecKillSessionsWithSkus;
import com.bxait.toymail.seckill.vo.SecKillSkuVo;
import com.bxait.toymail.seckill.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/*
@description:
@ClassName SecKillService
@author Chen
@create 2023-04-30 10:36
@Version 1.0
*/
@Slf4j
@Service("secKillService")
public class SecKillServiceImpl implements SecKillService {

    @Resource
    CouponFeignService couponFeignService;

    @Resource
    ProductFeignService productFeignService;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    RedissonClient redissonClient;

    @Resource
    RabbitTemplate rabbitTemplate;




    private final String SKU_KILL_CACHE_PREFIX = "secKill:skus";
    private final String SESSIONS_CACHE_PREFIX = "secKill:sessions:";
    // +商品随机码
    private final String SKU_STOCK_SEMAPHORE = "secKill:stock:";


    /*
     * @param 
     * @==============History===============<br/>
     * @Description //   上架最近需要秒杀的3天内的商品
     * @Date 2023/4/30 2023/4/30 
     * @Author < a href="2277411582@qq.com">chen</ a>
     * @return void
     */
    @Override
    public void uploadSecKillSkuLatest3Days() {
        // 1.扫描最近3天的秒杀活动
        R session = couponFeignService.getLate3DaySession();
        if(session.getCode() == 0){
            // 上架商品
            List<SecKillSessionsWithSkus> sessionData = session.getData(new TypeReference<List<SecKillSessionsWithSkus>>() {});
            //1、缓存活动信息
            saveSessionInfos(sessionData);
            //2、缓存活动的关联商品信息
            saveSessionSkuInfos(sessionData);
        }
    }

    /*
     * @param
     * @==============History===============<br/>
     * @Description //   获取当前时间内的秒杀的商品
     * @Date 2023/4/30 2023/4/30
     * @Author < a href="2277411582@qq.com">chen</ a>
     * @return java.util.List<com.bxait.toymail.seckill.to.SecKillSkuRedisTo>
     */
    @Override
    public List<SecKillSkuRedisTo> getCurrentSecKillSkus() {
        // 1.获取当前时间所属那个秒杀活动
        long currentTime = new Date().getTime();
        // 获取所有的活动
        Set<String> keys = stringRedisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        // 循环所有的活动,得到我们现在当前时间端所处的活动场次
        for (String key : keys) {
            // 先将key的前缀去掉
            key.replace(SESSIONS_CACHE_PREFIX,"");
            // 使用短横线将key进行分割
            String[] time = key.split("_");
            // 活动开始时间
            long startTime = Long.parseLong(time[0]);
            // 活动结束时间
            long endTime = Long.parseLong(time[1]);
            if(currentTime >= startTime && currentTime <= endTime){
                // 获取当前这个key活动的所以商品信息
                List<String> range = stringRedisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKU_KILL_CACHE_PREFIX);
                List<String> list = hashOps.multiGet(range);
                if(list != null && list.size() > 0){
                    List<SecKillSkuRedisTo> collect = list.stream().map(item -> {
                        SecKillSkuRedisTo redis = JSON.parseObject((String) item, SecKillSkuRedisTo.class);
                        redis.setRandomCode(null);
                        return redis;
                    }).collect(Collectors.toList());
                    return collect;
                }
                break;
            }
        }
        return null;
    }

    /*
     * @param skuId
     * @==============History===============<br/>
     * @Description //   获取当前商品的秒杀信息
     * @Date 2023/5/4 2023/5/4
     * @Author < a href="2277411582@qq.com">chen</ a>
     * @return com.bxait.toymail.seckill.to.SecKillSkuRedisTo
     */
    @Override
    public SecKillSkuRedisTo getSkuSecKillInfo(Long skuId) {
        // 1、找到所有需要参与秒杀的商品的key
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKU_KILL_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        if(keys != null && keys.size() > 0){
            // 6_4
            String regx = "\\d_" + skuId;
            for (String key : keys) {
                if (Pattern.matches(regx, key)) {
                    String json = hashOps.get(key);
                    SecKillSkuRedisTo skuRedisTo = JSON.parseObject(json, SecKillSkuRedisTo.class);
                    // TODO 加入非空判断
                    if (skuRedisTo == null) return null;
                    //随机码
                    long current = new Date().getTime();
                    if (current >= skuRedisTo.getStartTime() && current <= skuRedisTo.getEndTime()) {
                        // TODO
                    } else {
                        // TODO 当前商品已经过了秒杀时间要直接删除
                        hashOps.delete(key);
                        skuRedisTo.setRandomCode(null);
                    }
                    return skuRedisTo;
                }
            }
        }
        return null;
    }

    /*
     * @param killId     需要秒杀的商品id
     * @param key        秒杀随机码
     * @param num        需要秒杀的数量
     * @==============History===============<br/>
     * @Description //   秒杀商品
     * @Date 2023/5/4 2023/5/4
     * @Author < a href="2277411582@qq.com">chen</ a>
     * @return java.lang.String
     */
    @Override
    public String kill(String killId, String key, Integer num) {
        // 1.获取当前时间
        long s1 = System.currentTimeMillis();
        // 2.获取当前用户信息
        MemberRespVo respVo = LoginUserInterceptor.loginUser.get();

        // 3.获取当前秒杀商品的详细信息
        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(SKU_KILL_CACHE_PREFIX);

        String json = hashOps.get(killId);

        if(!StringUtils.isEmpty(json)){
            SecKillSkuRedisTo redis = JSON.parseObject(json, SecKillSkuRedisTo.class);
            // 校验合法性
            Long startTime = redis.getStartTime();
            Long endTime = redis.getEndTime();
            long time = new Date().getTime();
            long ttl = endTime - time;
            if (time >= startTime && time <= endTime) {
                // 校验随机码和商品id
                String randomCode = redis.getRandomCode();
                String skuId = redis.getPromotionSessionId() + "_" + redis.getSkuId();
                if (randomCode.equals(key) && killId.equals(skuId)) {
                    // 验证购物数量是否合理
                    if (num <= redis.getSecKillLimit()) {
                        // 验证这个人是否已经购买过。幂等性; 如果只要秒杀成功，就去占位。  userId_SessionId_skuId
                        String redisKey = respVo.getId() + "_" + skuId;
                        // 自动过期
                        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                        if (aBoolean) {
                            // 占位成功说明从来没有买过
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                            // 120  20ms
                            boolean b = semaphore.tryAcquire(num);
                            if (b){
                                String timeId = IdWorker.getTimeId();
                                SecKillOrderTo orderTo = new SecKillOrderTo();
                                orderTo.setOrderSn(timeId);
                                orderTo.setMemberId(respVo.getId());
                                orderTo.setNum(num);
                                orderTo.setPromotionSessionId(redis.getPromotionSessionId());
                                orderTo.setSkuId(redis.getSkuId());
                                orderTo.setSecKillPrice(redis.getSecKillPrice());
                                // 秒杀成功,发送一条消息导mq中
                                rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", orderTo);
                                long s2 = System.currentTimeMillis();
                                log.info("耗时...{}", (s2 - s1));
                                return timeId;
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /*
     * @param sessionData
     * @==============History===============<br/>
     * @Description //   缓存商品信息
     * @Date 2023/4/30 2023/4/30
     * @Author < a href="2277411582@qq.com">chen</ a>
     * @return void
     */
    protected void saveSessionSkuInfos(List<SecKillSessionsWithSkus> sessionData) {
        if(sessionData != null && sessionData.size() > 0){
            sessionData.stream().forEach(item->{
                BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKU_KILL_CACHE_PREFIX);
                List<SecKillSkuVo> relationSkus = item.getRelationSkus();
                for (SecKillSkuVo skus : relationSkus) {
                    // 秒杀商品上架时要为每一个商品准备一个随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    // 商品秒杀的活动场次id
                    String promotionSessionId = skus.getPromotionSessionId().toString();
                    // 商品id
                    String skuId = skus.getSkuId().toString();
                    // 当前商品是否上架    幂等性处理
                    if (!ops.hasKey( promotionSessionId + "_" + skuId)) {
                        // 缓存商品
                        SecKillSkuRedisTo redisTo = new SecKillSkuRedisTo();
                        // 1、sku的基本数据
                        R skuInfo = productFeignService.getSkuInfo(skus.getSkuId());
                        if (skuInfo.getCode() == 0) {
                            SkuInfoVo info = skuInfo.getData(new TypeReference<SkuInfoVo>() {});
                            redisTo.setSkuInfo(info);
                        }
                        // 2、sku的秒杀信息
                        BeanUtils.copyProperties(skus, redisTo);

                        // 3、设置上当前商品的秒杀时间信息
                        redisTo.setStartTime(item.getStartTime().getTime());
                        redisTo.setEndTime(item.getEndTime().getTime());

                        // 4、设置上当前商品的随机码
                        redisTo.setRandomCode(token);
                        String jsonString = JSON.toJSONString(redisTo);

                        // 每个商品的过期时间不一样。所以，我们在获取当前商品秒杀信息的时候，做主动删除，代码在 getSkuSecKillInfo 方法里面
                        ops.put(skus.getPromotionSessionId().toString() + "_" + skus.getSkuId().toString(), jsonString);
                        // 5、使用库存作为分布式的信号量  限流;
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                        // 商品可以秒杀的数量作为信号量
                        semaphore.trySetPermits(skus.getSecKillCount());
                        // 设置过期时间。
                        semaphore.expireAt(item.getEndTime());
                    }
                }
            });
        }
    }

    /*
     * @param sessionData
     * @==============History===============<br/>
     * @Description //   缓存活动信息
     * @Date 2023/4/30 2023/4/30
     * @Author < a href="2277411582@qq.com">chen</ a>
     * @return void
     */
    protected void saveSessionInfos(List<SecKillSessionsWithSkus> sessionData) {
        if(sessionData != null && sessionData.size() > 0){
            // 获取活动的开始时间和结束时间
            sessionData.stream().forEach(item->{
                // 活动开始时间
                Long startTime = item.getStartTime().getTime();
                // 活动结束时间
                Long endTime = item.getEndTime().getTime();
                // 判断当前活动是否已经上架       幂等性处理
                String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
                Boolean hasKey = stringRedisTemplate.hasKey(key);
                if(!hasKey){
                    List<String> collect = item.getRelationSkus().stream().map(rel -> {
                        // 商品秒杀的活动场次id
                        String promotionSessionId = rel.getPromotionSessionId().toString();
                        // 商品id
                        String skuId = rel.getSkuId().toString();
                        return promotionSessionId + "_" + skuId;
                    }).collect(Collectors.toList());
                    // 缓存活动信息
                    stringRedisTemplate.opsForList().leftPushAll(key, collect);
                    // 设置过期时间
                    stringRedisTemplate.expireAt(key, new Date(endTime));
                }
            });
        }
    }


}
